(function (global, factory) {
    "use strict";
    if (typeof module === "object" && typeof module.exports === "object") {
        module.exports = global.document ?
            factory(global, true) :
            function (w) {
                if (!w.document) {
                    throw new Error("flowchart requires a window with a document");
                }
                return factory(w);
            };
    } else {
        factory(global);
    }
})(typeof window !== "undefined" ? window : this, function (window, noGlobal) {
    "use strict";

    var POINT_NAME = "point";
    var POINT_COLOR = "#f6f7ff";
    var POINT_ACTIVITE_COLOR = "#d9230f";
    var DEBUG = true;
    var idCounter = 1;

    var NODE = {
        name: "group", // konva name
        type: "",
        color: "#f6f7ff", // konva fill
        borderColor: "#03689a", // konva stroke
        text: "",
        width: 200,
        height: 35,
        fontSize: 16,
        padding: 10,
        fontSize: 16,
        align: "center",
        fill: 'black',
        draggable: true,
        onClick: function (event, node) {
            if (DEBUG) console.log(`onClick `, arguments)
        },
        onDblclick: function (event, node) {
            if (DEBUG) console.log(`onDblclick `, arguments)
        },
        onDragstart: function (event, node) {
            if (DEBUG) console.log(`onDragstart `, arguments)
        },
        onDragmove: function (event, node) {
            if (DEBUG) console.log(`onDragmove `, arguments)
        },
        onDragend: function (event, node) {
            if (DEBUG) console.log(`onDragend `, arguments)
        },
        onSelected: function (event, node) {
            if (DEBUG) console.log(`onSelected `, arguments)
        },
    };

    var DEFAULT_NODE_TYPE_LIST = [{
            "type": "start",
            "text": "模型输入",
            color: "#ebf9dc"
        },
        {
            "type": "service",
            "text": "内部服务"
        },
        {
            "type": "end",
            "text": "模型输出",
            color: "#701321c2"
        },
    ];

    var DEFAULT_OPTIONS = {
        container: "",
        name: "container",
        width: window.innerWidth,
        height: window.innerHeight,
        onArrowClick: function (event, node) {
            if (DEBUG) console.log(`onArrowClick `, arguments)
        },
        onArrowDblclick: function (event, node) {
            if (DEBUG) console.log(`onArrowDblclick `, arguments)
        },
        panel: {
            name: "panel",
            draggable: false,
            width: 250,
            height: 300,
            nodes: []
        }, // 面板节点
    }

    var Flowchart = function (options) {
        this._options = $.extend(true, {}, DEFAULT_OPTIONS, options);
        if (!this._options.panel.nodes || this._options.panel.nodes.length == 0) {
            this._options.panel.nodes = DEFAULT_NODE_TYPE_LIST.map(function (val) {
                return $.extend({}, NODE, val)
            });
        } else {
            this._options.panel.nodes = this._options.panel.nodes.map(function (val) {
                return $.extend({}, NODE, val)
            });
        }

        this.init();
    }

    // 开始初始化流程
    Flowchart.prototype.init = function () {
        this.container = {};
        var container = this.container;
        container.stage = new Konva.Stage({
            container: this._options.container,
            width: this._options.width,
            height: this._options.height
        });
        container.layer = new Konva.Layer();

        let paddingRight = 50;
        container.panel = new Konva.Rect({
            x: this._options.width - this._options.panel.width - paddingRight,
            y: 30,
            width: this._options.panel.width,
            height: this._options.panel.height,
            fill: '#ffffff',
            stroke: '#ffe187',
            strokeWidth: 2
        });
        container.layer.add(container.panel);

        // 添加panel 面板
        let startX = this._options.width - this._options.panel.width - paddingRight / 2;
        let startY = 30 + 20;
        this._options.panel.nodes.forEach((node) => {
            let el = new Konva.Rect({
                name: "el",
                width: node.width,
                height: node.height,
                fill: node.color || '#ffffff',
                stroke: node.borderColor,
                strokeWidth: 2
            });
            let text = new Konva.Text({
                name: "nodeText",
                width: node.width,
                height: node.height,
                text: node.text,
                padding: node.padding || 10,
                fontSize: node.fontSize,
                align: node.align,
                fill: node.fill
            });
            let group = new Konva.Group({
                x: startX,
                y: startY,
                draggable: node.draggable,
                name: node.name
            });
            // 初始化事件监听
            group.on("dragstart", this.dragstartHandler.bind(this));
            group.on("dragend", evt => container.stage.container().style.cursor = 'default');
            // 保存节点参数到konva对象中
            group.$data = $.extend({}, node);
            // 添加到布局中
            group.add(el);
            group.add(text);
            container.layer.add(group);
            startY = startY + node.height + 20;
        });

        container.tr = new Konva.Transformer({
            resizeEnabled: false,
            rotateEnabled: false,
            borderStrokeWidth: 4
        });
        container.layer.add(container.tr);
        container.stage.add(container.layer);
        container.stage.on("mousemove", this.updateCombineArrow.bind(this));
        container.stage.on('click tap dragstart', this.transformerClickHandler.bind(this));
        // 添加取消事件处理
        $(window).keyup((e) => {
            if (e.keyCode === 27) {
                this.cancelCombineArrow.apply(this);
            } else {
                return;
            }
            e.preventDefault();
            container.layer.draw();
        });

    }

    Flowchart.prototype.transformerClickHandler = function (evt) {
        let container = this.container;
        let stage = container.stage;
        let layer = container.layer;
        let tr = container.tr;

        if (evt.target === stage) {
            tr.nodes([]);
            layer.draw();
            return;
        }
        let target = evt.target;
        if (target.getClassName() == "Arrow") {
            // call arrow onSelected event handler
            return;
        }

        if (target.hasName(POINT_NAME)) {
            return;
        }

        // target on group
        while (target.getClassName() != "Group") {
            target = target.getParent();
            if (!target) {
                return;
            }
        }
        tr.nodes([target]);
        // call node onSelected event
        if (target.$data.onSelected) {
            target.$data.onSelected(evt, target.$data, target, this);
        }
        // tr.moveToBottom();
        layer.draw();
    }

    Flowchart.prototype.dragstartHandler = function (evt) {
        let container = this.container;
        let stage = container.stage;
        let layer = container.layer;
        let group = evt.target;
        // 鼠标样式
        stage.container().style.cursor = 'move';
        // 克隆一个新对象 停留在原处，给下一次拖拽新增
        let newGroup = group.clone();
        newGroup.$data = $.extend({}, group.$data);
        layer.add(newGroup);

        group.id(idCounter++); // 设置id
        // 初始化事件监听
        this.initGroupEvent(group);
        // refresh 
        layer.draw();
    }

    Flowchart.prototype.initGroupEvent = function (group) {
        let container = this.container;
        let stage = container.stage;
        let layer = container.layer
        let node = group.$data;

        group.off("dragstart");
        group.on("click", (event) => {
            node.onClick(event, group, node)
        });
        group.on("dblclick", (event) => {
            node.onDblclick(event, group, node)
        });
        group.on("dragstart", (event) => {
            stage.container().style.cursor = 'move';
            node.onDragstart(event, group, node)
        });
        group.on("dragmove", (event) => {
            this.updateCombineDoneByDrag.call(this, event, group)
        });
        group.on("dragmove", (event) => {
            node.onDragmove(event, group, node)
        });
        group.on("dragend", (event) => {
            node.onDragend(event, group, node)
        });

        let elH = group.$data.height;
        let elW = group.$data.width;
        let self = this;
        group.on("mouseover", function (e) {
            stage.container().style.cursor = 'move';
            let mp = stage.getPointerPosition();
            let tp = e.target.absolutePosition();
            let h = elH;
            let w = elW;
            // 判断是在上还是在下
            let pointConfig = {
                name: POINT_NAME,
                radius: 7,
                fill: POINT_COLOR,
                stroke: '#03689a',
                strokeWidth: 2,
            }
            let points = group.find(".point");
            if (!points || points.toArray().length == 0) {
                let t = new Konva.Circle($.extend({
                    id: idCounter++,
                    x: w / 2,
                    y: 0
                }, pointConfig));
                t._positionType = "t";
                let b = new Konva.Circle($.extend({
                    id: idCounter++,
                    x: w / 2,
                    y: h
                }, pointConfig));
                b._positionType = "b";
                let l = new Konva.Circle($.extend({
                    id: idCounter++,
                    x: 0,
                    y: h / 2
                }, pointConfig));
                l._positionType = "l";
                let r = new Konva.Circle($.extend({
                    id: idCounter++,
                    x: w,
                    y: h / 2
                }, pointConfig));
                r._positionType = "r";
                //point.on("click tap", lineClickHandler);
                let ps = [t, b, l, r];
                group.add.apply(group, ps);
                ps.forEach(p => p.on("click tap", self.pointClick.bind(self)));
            } else {
                points.each(p => p.show());
            }
            // refresh 
            layer.draw();
        });
        group.on("mouseout", function (evt) {
            stage.container().style.cursor = 'default';
            self.refreshGroupPoints(group, false);
            // refresh 
            layer.draw();
        });
    }

    Flowchart.prototype.getGroupActivePoint = function (group) {
        let active;
        let points = group.find(".point");
        for (let i = 0; i < points.length; i++) {
            let p = points[i];
            if (p.activeFlag) {
                active = p;
                break;
            }
        }
        return active;
    }

    // 拖拽group时， 将连接也联动拖拽
    Flowchart.prototype.updateCombineDoneByDrag = function (evt, group) {
        let activePoint = this.getGroupActivePoint(group);
        let container = this.container;
        let combineDone = container.combineDone;

        if (combineDone) {
            combineDone.forEach(combine => {
                if (combine[0] === group || combine[2] === group) {
                    let headGroup = combine[0];
                    let tailGroup = combine[2];
                    // 原点
                    let originX = headGroup.absolutePosition().x + headGroup.width() / 2;
                    let originY = headGroup.absolutePosition().y + headGroup.height() / 2;
                    // 对点
                    let targetX = tailGroup.absolutePosition().x + tailGroup.width() / 2;
                    let targetY = tailGroup.absolutePosition().y + tailGroup.height() / 2;

                    let arrow = combine[4];
                    let sin45 = Math.sin(Math.PI / 180 * 30);

                    let x = targetX - originX;
                    let y = targetY - originY;
                    let r = Math.sqrt(x * x + y * y);
                    /**
                     *        |
                     *    c   |   b
                     *        |
                     * ---------------
                     *        |
                     *    d   |   a
                     *        |
                     * 
                     */
                    let ps = arrow.points();
                    let position, tposition;
                    let sin = Math.abs(y / r);
                    if (x > 0 && y > 0) {
                        // position a
                        position = sin > sin45 ? "b" : "r";
                        tposition = sin > sin45 ? "t" : "l";
                    } else if (x > 0 && y < 0) {
                        // position b
                        position = sin > sin45 ? "t" : "r";
                        tposition = sin > sin45 ? "b" : "l";
                    } else if (x < 0 && y < 0) {
                        // position c
                        position = sin > sin45 ? "t" : "l";
                        tposition = sin > sin45 ? "b" : "r";
                    } else if (x < 0 && y > 0) {
                        // position d
                        position = sin > sin45 ? "b" : "l";
                        tposition = sin > sin45 ? "t" : "r";
                    }
                    if (position) {
                        let opoints = headGroup.find(".point");
                        let oCircle, tCircle;
                        for (let i = 0; i < opoints.length; i++) {
                            if (opoints[i]._positionType == position) {
                                oCircle = opoints[i];
                                break;
                            }
                        }
                        combine[1] = oCircle;
                        let tpoints = tailGroup.find(".point");
                        for (let i = 0; i < tpoints.length; i++) {
                            if (tpoints[i]._positionType == tposition) {
                                tCircle = tpoints[i];
                                break;
                            }
                        }
                        combine[3] = tCircle;
                        let offset = this.calcOffset(oCircle.absolutePosition().x, oCircle.absolutePosition().y,
                            tCircle.absolutePosition().x, tCircle.absolutePosition().y, 7);
                        ps[0] = oCircle.absolutePosition().x + offset[0];
                        ps[1] = oCircle.absolutePosition().y + offset[1];
                        ps[2] = tCircle.absolutePosition().x - offset[0];
                        ps[3] = tCircle.absolutePosition().y - offset[1];
                        //console.log(`ps ${ps}, origin ${position}, target ${tposition} sin ${sin} sin45 ${sin45} asin ${Math.asin(sin)/(Math.PI / 180)}`);
                    }
                    arrow.points(ps);
                }
            });
        }
    }

    // 计算偏移 coordinate
    Flowchart.prototype.calcOffset = function (ox, oy, tx, ty, offset) {
        let x = tx - ox;
        let y = ty - oy;
        let r = Math.sqrt(x * x + y * y);
        let sin = y / r;
        let cos = x / r;
        let r1 = r - offset;
        let x1 = x - cos * r1;
        let y1 = y - sin * r1;
        return [x1, y1];
    }

    // 刷新绘制过程的箭头 随鼠标动
    Flowchart.prototype.updateCombineArrow = function (evt) {
        let container = this.container;
        let stage = container.stage;
        let layer = container.layer;
        let combine = container.combine;
        if (combine) {
            let mp = stage.getPointerPosition();
            let tp = combine[1].absolutePosition();
            let arrowps = combine[4].points();
            // 原点
            let originX = arrowps[0];
            let originY = arrowps[1];
            let offset = this.calcOffset(tp.x, tp.y, mp.x, mp.y, 7);
            arrowps[0] = tp.x + offset[0];
            arrowps[1] = tp.y + offset[1];
            arrowps[2] = mp.x - offset[0];
            arrowps[3] = mp.y - offset[1];
            combine[4].points(arrowps);
            layer.draw();
        }
    }
    // 取消取连接 e.keyCode === 27 ESC or dblclick
    Flowchart.prototype.cancelCombineArrow = function (evt) {
        let container = this.container;
        let combine = container.combine;
        if (combine && combine[2] == null) {
            this.togglePointActive(combine[1], false);
            let arrow = combine[4];
            arrow.hide();
            arrow.destroy();
            container.combine = null;
        }
    }

    // 更新点的激活状态
    Flowchart.prototype.togglePointActive = function (point, activeFlag) {
        if (point) {
            if (typeof activeFlag === "undefined") {
                point.activeFlag = !point.activeFlag;
            } else {
                point.activeFlag = activeFlag;
            }
            point.fill(point.activeFlag ? POINT_ACTIVITE_COLOR : POINT_COLOR);
        }
        return point;
    }

    // 更新点的
    Flowchart.prototype.refreshGroupPoints = function (group, show) {
        if (group.children) {
            let points = group.find(".point");
            let hasActive = false;
            points.each(p => {
                if (p.activeFlag) {
                    hasActive = true;
                }
            });
            if (points && !hasActive) points.each(p => p[show ? "show" : "hide"]());
        }
    }

    // point 点击连线
    Flowchart.prototype.pointClick = function (evt) {
        let container = this.container;
        let stage = container.stage;
        let layer = container.layer;
        let point = evt.target;
        if (point.hasName(POINT_NAME)) {
            let group = point.getParent();
            this.togglePointActive(point);
            // 清除其它的激活状态
            let points = group.find(".point");
            if (points) {
                points.each(p => {
                    if (!(p === point)) {
                        this.togglePointActive(p, false);
                    }
                });
            }
            // 连接处理部分
            if (point.activeFlag) {

                let mp = stage.getPointerPosition();
                let tp = point.absolutePosition();
                // 判断是否当前有头部对象 没有则初始化 初始后直接返回 等待用户选择下一个点
                if (!container.combine) {
                    // 记录当前连接的对象   头    头的点    尾     尾的点  箭头
                    container.combine = [group, point, null, null, null];
                    let offset = this.calcOffset(tp.x, tp.y, mp.x, mp.y, 7);
                    // 绘制连线 
                    let arrow = new Konva.Arrow({
                        x: 0,
                        y: 0,
                        points: [tp.x, tp.y, mp.x - offset[0], mp.y - offset[1]],
                        pointerLength: 15,
                        pointerWidth: 15,
                        fill: '#808080',
                        stroke: '#808080',
                        strokeWidth: 3
                    });
                    container.combine[4] = arrow;
                    // 判断初始化连接完成的集合
                    if (!container.combineDone) {
                        container.combineDone = [];
                    }
                    layer.add(arrow);
                    layer.draw();
                    return;
                }

                // 用户选择了下一个点， 进行两点连线
                let combine = container.combine;
                if (group === combine[0]) {
                    // 不能自己连自己
                    return;
                }
                container.combine = null; // 释放当前连接  给下一次让位

                combine[2] = group;
                combine[3] = point;
                // 推入完成集合中
                container.combineDone.push(combine);
                let arrowps = combine[4].points();
                let offset = this.calcOffset(arrowps[0], arrowps[1], tp.x, tp.y, 7);
                arrowps[2] = tp.x - offset[0];
                arrowps[3] = tp.y - offset[1];
                this.togglePointActive(combine[1], false);
                this.togglePointActive(combine[3], false);
                this.refreshGroupPoints(combine[0], false);
                // 绑定事件
                combine[4].on("click tap", (event) => {
                    this._options.onArrowClick(event, combine[0].$data, combine[2].$data);
                });
                combine[4].on("dblclick dbltap", (event) => {
                    this._options.onArrowDblclick(event, combine[0].$data, combine[2].$data);
                });
                combine[4].id(idCounter++); // 设置id
                layer.draw();
                return;
            }

            layer.draw();
        }
    }

    // 加载流程图
    Flowchart.prototype.load = function (data = {}) {
        let flowNodes = data.nodes || [];
        let links = data.links || [];
        let container = this.container;
        let layer = container.layer;
        let maxId = idCounter;
        // 判断初始化连接完成的集合
        if (!container.combineDone) {
            container.combineDone = [];
        }

        let panelNodes = this._options.panel.nodes;
        let nodeMap = {};
        panelNodes.forEach(node => nodeMap[node.type] = node);

        flowNodes.forEach(node => {
            let obj = Konva.Node.create(node);
            let $$data = nodeMap[node.$data.type];
            obj.$data = $.extend({}, $$data, node.$data);
            let points = obj.find(".point");
            points.forEach(child => {
                // 统计最大id
                if (maxId < child.id()) {
                    maxId = child.id();
                }
                for (let i = 0; i < node.children.length; i++) {
                    let point = node.children[i];
                    if (child.id() == point.attrs.id * 1 && point.className === "Circle") {
                        child._positionType = point._positionType;
                        break;
                    }
                }
                // 点的事件初始化
                child.on("click tap", this.pointClick.bind(this))
            });
            // 初始化事件监听
            this.initGroupEvent(obj);
            // 统计最大id
            if (maxId < node.attrs.id * 1) {
                maxId = node.attrs.id * 1;
            }
            layer.add(obj);
        });
        links.forEach(link => {
            let arrow = Konva.Node.create(link);
            let connect = link.connect;
            let combine = [];
            // 找头找尾找到点
            layer.children.forEach(el => {
                if (el.id() === connect[0] * 1) {
                    combine[0] = el;
                    let points = el.find(".point");
                    for (let i = 0; i < points.length; i++) {
                        if (points[i]._positionType == connect[1]) {
                            combine[1] = points[i];
                            break;
                        }
                    }
                } else if (el.id() === connect[2] * 1) {
                    combine[2] = el;
                    let points = el.find(".point");
                    for (let i = 0; i < points.length; i++) {
                        if (points[i]._positionType == connect[3]) {
                            combine[3] = points[i];
                            break;
                        }
                    }
                }
            });
            combine[4] = arrow;
            layer.add(arrow);
            container.combineDone.push(combine);
            // 初始化事件监听
            arrow.on("click tap", (event) => {
                this._options.onArrowClick(event, combine[0].$data, combine[2].$data);
            });
            arrow.on("dblclick dbltap", (event) => {
                this._options.onArrowDblclick(event, combine[0].$data, combine[2].$data);
            });
            // 统计最大id
            if (maxId < link.attrs.id * 1) {
                maxId = link.attrs.id * 1;
            }
        });
        // 刷新画布
        layer.draw();
        idCounter = ++maxId;
    }
    // 导出流程图
    Flowchart.prototype.export = function () {
        // 面板节点信息
        let panelNodes = this._options.panel.nodes;
        let nodeMap = {};
        panelNodes.forEach(node => nodeMap[node.type] = node);
        let container = this.container;
        let layer = container.layer;

        let flowNodes = [],
            links;
        layer.children.forEach(child => {
            if (child.getClassName() == "Group" && !!nodeMap[child.$data.type]) {
                let nodeObj = child.toObject();
                nodeObj.$data = child.$data;
                let points = child.find(".point");
                if (!points || points.length == 0) {
                    return;
                }
                // 记录点的类型
                nodeObj.children.forEach(child => {
                    if (child.className === "Circle") {
                        for (let i = 0; i < points.length; i++) {
                            if (child.attrs.id == points[i].id()) {
                                child._positionType = points[i]._positionType;
                                break;
                            }
                        }
                    }
                });
                flowNodes.push(nodeObj);
            }
        });
        links = container.combineDone.map(link => {
            let linkObj = link[4].toObject();
            linkObj.connect = [link[0].id(), link[1]._positionType, link[2].id(), link[3]._positionType];
            return linkObj
        });
        return {
            nodes: flowNodes,
            links: links
        };
    }

    if (typeof noGlobal === "undefined") {
        window.Flowchart = Flowchart;
    }

    return Flowchart;
})